package com.tuniu.agents.tools.hotel.tool;

import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.tuniu.agents.advisor.ChatEnvironmentMemory;
import com.tuniu.agents.common.AgentsConstants;
import com.tuniu.agents.message.ToolLoggerInfo;
import com.tuniu.agents.tools.hotel.HotelProperties;
import com.tuniu.agents.tools.hotel.constant.HotelConstants;
import com.tuniu.agents.util.ConversationUtil;
import com.tuniu.agents.tools.hotel.utils.HotelCommonUtil;
import com.tuniu.agents.util.HttpManager;
import com.tuniu.agents.util.JsonUtil;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.BiFunction;

/**
 * Tool for retrieving the lowest price hotel package list
 */
public class PlowestPriceHotelList implements BiFunction<PlowestPriceHotelList.Request, ToolContext, PlowestPriceHotelList.Response> {

    private static final Logger log = LoggerFactory.getLogger(PlowestPriceHotelList.class);

    public static final String TOOLNAME = "hotel_package_list";

    @Autowired
    private HttpManager httpManager;

    @Autowired
    private ChatEnvironmentMemory chatEnvironmentMemory;

    private final String url;
    private final String accessKey;

    /**
     * Constructor that initializes the hotel package list URL and access key
     * 
     * @param properties Hotel properties containing configuration
     */
    public PlowestPriceHotelList(HotelProperties properties) {
        this.url = properties.getHotelPackageListUrl();
        this.accessKey = properties.getAccessKey();
    }

    @ToolLoggerInfo(beanName = "hotelToolLoggingService")
    @Override
    public Response apply(Request request, ToolContext toolContext) {
        Request newRequest = null;
        try {
            log.info("PlowestPriceHotelList input:{}", JsonUtil.toJson(request));
            newRequest = processRequestParameters(request, toolContext);
            addChatEnvironmentMemory(ConversationUtil.getConversationId(toolContext), newRequest);
            return httpManager.post(url, newRequest, Response.class, Map.of("accessKey", accessKey));
        } catch (Exception e) {
            log.error("PlowestPriceHotelList apply error request:{}", JsonUtil.toJson(newRequest), e);
            return null;
        }
    }

    /**
     * Process and prepare request parameters with default values if needed
     * 
     * @param request Original request
     * @param toolContext Tool context
     * @return Processed request with filled parameters
     */
    private Request processRequestParameters(Request request, ToolContext toolContext) {
        return new Request(
                HotelCommonUtil.getDefaultCityName(request.cityName(), request.poiName(), toolContext),
                request.journeyNum(),
                request.checkIn(),
                request.checkOut(),
                request.poiName(),
                request.keyword(),
                request.prices(),
                request.pageNum()
        );
    }

    /**
     * Add request information to chat environment memory
     * 
     * @param conversationId Conversation ID
     * @param request Request object
     */
    private void addChatEnvironmentMemory(String conversationId, Request request) {
        Map<String, Object> map = new HashMap<>();
        map.put(conversationId + "_LAST_ACTION","package_query");
        map.put(conversationId + "_request",request);
        if (StringUtils.hasText(request.cityName())){
            map.put(AgentsConstants.AgentsConversationEnvironmentKey.PARAM_DEPARTURE_CITY_NAME,request.cityName());
        }
        chatEnvironmentMemory.add(conversationId, map);
    }

    /**
     * Build a new package row with URL information
     * 
     * @param packageRow Original package row
     * @return Updated package row with URL information
     */
    public static PackageRow buildNewData(PackageRow packageRow) {
        return new PackageRow(
                packageRow.packageId(),
                packageRow.packageType(),
                packageRow.packageName(),
                packageRow.vendorId(),
                packageRow.subVendorId(),
                packageRow.checkIn(),
                packageRow.checkOut(),
                packageRow.hotelId(),
                packageRow.roomId(),
                packageRow.roomName(),
                packageRow.packagePictures(),
                packageRow.packagePrice(),
                packageRow.journeyNum(),
                packageRow.hotelName(),
                packageRow.commentScore(),
                packageRow.star(),
                packageRow.starName(),
                packageRow.firstPic(),
                packageRow.packageItemList(),
                packageRow.cityName(),
                packageRow.cityCode(),
                buildNewCommonUrl(packageRow)
        );
    }

    /**
     * Build common URL object with app and mini-program URLs
     * 
     * @param packageRow Package row data
     * @return Common URL object with relevant links
     */
    private static CommonUrl buildNewCommonUrl(PackageRow packageRow) {
        return new CommonUrl(
                buildAppDetailUrl(packageRow),
                Strings.EMPTY,
                buildMpDetailUrl(packageRow)
        );
    }

    /**
     * Build app detail URL for the package
     * 
     * @param packageRow Package row data
     * @return App detail URL
     */
    private static String buildAppDetailUrl(PackageRow packageRow) {
        try {
            Map<String, Object> urlParams = new LinkedHashMap<>();
            urlParams.put("packageId", packageRow.packageId());
            urlParams.put("packageType", packageRow.packageType());
            urlParams.put("vendorId", packageRow.vendorId());
            urlParams.put("subVendorId", packageRow.subVendorId());
            urlParams.put("hotelId", packageRow.hotelId());
            urlParams.put("roomId", packageRow.roomId());
            urlParams.put("packagePrice", Optional.ofNullable(packageRow.packagePrice()).orElse(0.0).toString());
            urlParams.put("journeyNum", packageRow.journeyNum());
            urlParams.put("checkIn", packageRow.checkIn());
            urlParams.put("rid", HotelConstants.DEFAULT_RID);
            urlParams.put("pid", HotelConstants.DEFAULT_PID);

            String jsonParams = JsonUtil.toJson(urlParams);
            String encodedParams = URLEncoder.encode(jsonParams, StandardCharsets.UTF_8);
            return HotelConstants.APP_PKG_DETAIL_DETAIL_PREFIX + encodedParams;
        } catch (Exception e) {
            log.error("Failed to generate App package detail link", e);
        }
        return Strings.EMPTY;
    }

    /**
     * Build mini-program detail URL for the package
     * 
     * @param packageRow Package row data
     * @return Mini-program detail URL
     */
    private static String buildMpDetailUrl(PackageRow packageRow) {
        try {
            Map<String, Object> urlParams = new LinkedHashMap<>();
            urlParams.put("hotelId", packageRow.hotelId());
            urlParams.put("checkIn", packageRow.checkIn());
            urlParams.put("checkOut", packageRow.checkOut());
            urlParams.put("roomNum", 1);
            urlParams.put("adultNum", 2);
            urlParams.put("childNum", 0);
            urlParams.put("childAges", Strings.EMPTY);
            urlParams.put("rid", HotelConstants.DEFAULT_RID);
            urlParams.put("pid", HotelConstants.DEFAULT_PID);

            Map<String, Object> locationParams = new HashMap<>();
            locationParams.put("cityCode", packageRow.cityCode());
            locationParams.put("cityName", packageRow.cityName());
            locationParams.put("cityType", HotelConstants.DEFAULT_CITY_TYPE);
            urlParams.put("curUserLocation", locationParams);

            String jsonParams = JsonUtil.toJson(urlParams);
            String encodedParams = URLEncoder.encode(jsonParams, StandardCharsets.UTF_8);
            return HotelConstants.MP_DETAIL_URL_PREFIX + encodedParams;
        } catch (Exception e) {
            log.error("Failed to generate mini-program package detail link", e);
        }
        return Strings.EMPTY;
    }


    @JsonClassDescription("酒店套餐查询参数")
    public record Request(
            @JsonProperty(value = "cityName") @JsonPropertyDescription("城市名称") String cityName,
            @JsonProperty(value = "journeyNum") @JsonPropertyDescription("行程天数") Integer journeyNum,
            @JsonProperty(required = true, value = "checkIn") @JsonPropertyDescription("入住日期") String checkIn,
            @JsonProperty(required = true, value = "checkOut") @JsonPropertyDescription("离店日期") String checkOut,
            @JsonProperty(value = "poiName") @JsonPropertyDescription("POI名称") String poiName,
            @JsonProperty(value = "keyword") @JsonPropertyDescription("其他信息") String keyword,
            @JsonProperty(value = "prices") @JsonPropertyDescription("价格") String prices,
            @JsonProperty(value = "pageNum") @JsonPropertyDescription("页码") Integer pageNum) {
    }

    @JsonClassDescription("酒店套餐查询返参")
    public record Response(
            @JsonProperty(required = true,
                    value = "success") @JsonPropertyDescription("成功标识，true：\"查询成功！\"，false：\"查询失败！\"") Boolean success,
            @JsonProperty(value = "errorCode") @JsonPropertyDescription("错误码") String errorCode,
            @JsonProperty(value = "data") @JsonPropertyDescription("酒店套餐数据") PackageData data) {
    }

    @JsonClassDescription("酒店套餐数据")
    public record PackageData(
            @JsonProperty(value = "rows") @JsonPropertyDescription("套餐列表") List<PackageRow> rows,
            @JsonProperty(value = "cityCode") @JsonPropertyDescription("城市code") Integer cityCode,
            @JsonProperty(value = "cityName") @JsonPropertyDescription("城市名称") String cityName,
            @JsonProperty(value = "totalPageNum") @JsonPropertyDescription("总页码") Integer totalPageNum,
            @JsonProperty(value = "queryId") @JsonPropertyDescription("查询快照ID") String queryId) {
    }

    @JsonClassDescription("套餐列表")
    public record PackageRow(
            @JsonProperty(value = "packageId") @JsonPropertyDescription("套餐Id") String packageId,
            @JsonProperty(value = "packageType") @JsonPropertyDescription("套餐类型") Integer packageType,
            @JsonProperty(value = "packageName") @JsonPropertyDescription("套餐名称") String packageName,
            @JsonProperty(value = "vendorId") @JsonPropertyDescription("供应商Id") Integer vendorId,
            @JsonProperty(value = "subVendorId") @JsonPropertyDescription("子供应商Id") Integer subVendorId,
            @JsonProperty(value = "checkIn") @JsonPropertyDescription("入住日期") String checkIn,
            @JsonProperty(value = "checkOut") @JsonPropertyDescription("离店日期") String checkOut,
            @JsonProperty(value = "hotelId") @JsonPropertyDescription("酒店Id") Long hotelId,
            @JsonProperty(value = "roomId") @JsonPropertyDescription("房型Id") Long roomId,
            @JsonProperty(value = "roomName") @JsonPropertyDescription("房型名称") String roomName,
            @JsonProperty(value = "packagePictures") @JsonPropertyDescription("套餐图片") List<String> packagePictures,
            @JsonProperty(value = "packagePrice") @JsonPropertyDescription("套餐价格") Double packagePrice,
            @JsonProperty(value = "journeyNum") @JsonPropertyDescription("行程天数") Integer journeyNum,
            @JsonProperty(value = "hotelName") @JsonPropertyDescription("酒店名称") String hotelName,
            @JsonProperty(value = "commentScore") @JsonPropertyDescription("评论分数") Double commentScore,
            @JsonProperty(value = "star") @JsonPropertyDescription("星级code") Integer star,
            @JsonProperty(value = "starName") @JsonPropertyDescription("星级名称") String starName,
            @JsonProperty(value = "firstPic") @JsonPropertyDescription("首图") String firstPic,
            @JsonProperty(value = "packageItemList") @JsonPropertyDescription("套餐明细") List<PackageItemDetail> packageItemList,
            @JsonProperty(value = "cityName") @JsonPropertyDescription("城市名称") String cityName,
            @JsonProperty(value = "cityCode") @JsonPropertyDescription("城市code") Integer cityCode,
            @JsonProperty(value = "url") @JsonPropertyDescription("url地址") CommonUrl url) {
    }

    @JsonClassDescription("套餐子项明细")
    public record PackageItemDetail(
            @JsonProperty(value = "itemType") @JsonPropertyDescription("子项类型") String itemType,
            @JsonProperty(value = "quantity") @JsonPropertyDescription("数量") Integer quantity,
            @JsonProperty(value = "itemName") @JsonPropertyDescription("子项名称") String itemName) {
    }

    @JsonClassDescription("url")
    public record CommonUrl(
            @JsonProperty(value = "appUrl") @JsonPropertyDescription("appUrl") String appUrl,
            @JsonProperty(value = "mUrl") @JsonPropertyDescription("m站url") String mUrl,
            @JsonProperty(value = "mpUrl") @JsonPropertyDescription("微信小程序Url") String mpUrl) {
    }

}
